import { INestApplication } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing'
import { AppModule } from '../src/app.module'
import { setupApp } from '../src/setup'
import datasource from '../ormconfig'
import { DataSource } from 'typeorm'
import { readFileSync } from 'fs'
import { join } from 'path'

//方法一：const app = new AppFactory().init init-> return app实例
//方法二：OOP(面向对象，符合nestjs) get instance():获取实例 -> app,private app,AppFactory constructor的部分进行初始化
// 使用：(1) cosnt appFactory = AppFactory.init() -> 静态方法初始化app实例
//       (2) const app = appFactory.instance():获取app实例
export class AppFactory {
  //为什么这里直接在类体中声明而不在构造函数上声明：
  //因为在类体中声明允许在其他地方初始化这个属性，可以根据需要推迟初始化或进行其他自定义操作
  //在构造函数上声明简单明了，每次实例化都会自动初始化，当创建该类的新实例时，就被创建并初始化

  //声明DataSource 类型的属性 connection =>  DataSource 类用于管理与数据库的连接
  connection: DataSource //这里需要之后通过实例方法初始化数据库等自定义操作，所以直接在类中声明
  constructor(private app: INestApplication) {}

  //获取app实例
  get instance() {
    return this.app
  }

  // 1.初始化app实例：静态方法
  static async init() {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule]
    }).compile()
    const app = moduleFixture.createNestApplication()
    setupApp(app)
    const port = 3000
    await app.listen(process.env.PORT ?? port)
    await app.init()

    //自己调用自己，将初始化的app传给自己
    return new AppFactory(app)
  }

  // 2.初始化db数据库
  async initDB() {
    // datasource.isInitialized(布尔值): 用来检查数据库连接是否已经成功初始化 =>!datasource.isInitialized(没有初始化)
    if (!datasource.isInitialized) {
      //datasource.initialize(): 用于 异步初始化数据库连接。
      await datasource.initialize()
    }
    //在数据库初始化后，赋值给this.connection，用于后续的数据库操作
    this.connection = datasource
    //测试的基础字典数据应在测试时写入到数据库中
    // Method1(需要关闭db_SYNC):this.connection.runMigrations()
    //      this.connection.runMigrations() 是 TypeORM 中用于执行数据库迁移（migrations）的方法。
    // Method2:直接写入SQL语句
    // 创建一个 QueryRunner 实例。QueryRunner 是 TypeORM 中的一个对象，它用来执行数据库操作，支持事务控制、SQL 执行等功能。
    const queryRunner = this.connection.createQueryRunner()
    // 获取数据库文件
    const sql =
      readFileSync(join(__dirname, '../src/migrations/init.sql'))
        .toString()
        .replace(/\r?\n|\r/g, '')
        ?.split(';') || []

    for (let i = 0; i < sql.length; i++) {
      await queryRunner.query(sql[i])
    }
  }

  // 3.清除数据库数据库 -> 避免测试数据污染

  // 清空数据库中所有实体表的数据
  async cleanupDB() {
    //connection.entityMetadatas 获取当前数据库连接中的所有实体的元数据。
    //entityMetadatas是一个包含所有实体类信息的数组，通常是与数据库表对应的类
    const entities = this.connection.entityMetadatas //获取数据库
    // for...of遍历 直接访问对象的元素，而 for...in遍历 用于获取对象的键。
    for (const entity of entities) {
      //   console.log('🚀 ~ AppFactory ~ cleanup ~ entity:', entity)
      //entity.name 是当前实体的类名
      //connection.getRepository(Entity): 用于获取与某个实体类（表）相关的 Repository 对象，该对象提供了对该实体的CRUD等操作,Entity是定义的实体类
      const repository = this.connection.getRepository(entity.name)
      //repository.query() 执行原生 SQL 查询 entity.tableName 是实体对应的数据库表名
      await repository.query(`DELETE FROM ${entity.tableName}`)
    }
  }

  // 4.断开与数据库的连接 -> 避免数据库连接太多而无法连接
  async destoryDB() {
    // connection.destroy()：销毁数据库连接，通常用于关闭连接或释放与数据库的资源。
    await this.connection?.destroy()
  }
}
